using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using Game.Network;

namespace Game.Network {
    public class TCPClient: IDisposable {
        public enum TCPState {
            Connecting,
            Idle,
            Sending
        };

        private Socket m_socket;
        private TCPState m_state;

        private byte[] m_recvBuffer;
        private int m_recvBufferLength;
        private int m_recvBufferOffset;

        private List<byte[]> m_recvQueue;
        private List<byte[]> m_sendQueue;
        private object m_sendLock;
        private object m_recvLock;

        private int[] m_sizes;

        public TCPState State { get { return m_state; } }

        public TCPClient (string hostname, int port) {
            m_socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            m_recvBufferLength = 65536;
            m_recvBufferOffset = 0;
            m_recvBuffer = new byte[m_recvBufferLength];

            m_recvQueue = new List<byte[]> ();
            m_sendQueue = new List<byte[]> ();
            m_sendLock = new object ();
            m_recvLock = new object ();
            m_sizes = new int[256];
            for (int i=0; i<=255; i++) { m_sizes[i] = 0; }

            m_state = TCPState.Connecting;
            m_socket.BeginConnect (hostname, port, new AsyncCallback(OnConnect), this);
        }

        public void Dispose(){
            m_socket.Disconnect (true);
        }

        private static void OnConnect(IAsyncResult result){
            try{
                TCPClient client = (TCPClient) result.AsyncState;
                client.m_socket.EndConnect(result);
                client.m_state = TCPState.Idle;
                client.m_socket.BeginReceive(client.m_recvBuffer, client.m_recvBufferOffset, client.m_recvBufferLength, SocketFlags.None, new AsyncCallback(OnReciveAsync), client);
            } catch(Exception e){
                throw e;
            }
        }

        private static void OnReciveAsync( IAsyncResult result){
            try{
                TCPClient client = (TCPClient) result.AsyncState;
                int bytesRead = client.m_socket.EndReceive(result);
                client.m_recvBufferOffset += bytesRead;

                bool loop = true;
                while(loop){
                    loop = false;
                    if(client.m_recvBufferOffset >= 1){
                        int size = client.m_sizes[client.m_recvBuffer[0]];
                        if((size == 0)&&(client.m_recvBufferOffset >= 3)){
                            size = client.m_recvBuffer[1] + client.m_recvBuffer[2] >> 8;
                        }
                        if((size > 0) && (client.m_recvBufferOffset >= size)){
                            byte[] packet = new byte[size];
                            Buffer.BlockCopy(client.m_recvBuffer, 0, packet, 0, size);
                            client.m_recvBufferOffset -= size;
                            Buffer.BlockCopy(client.m_recvBuffer, size, client.m_recvBuffer, 0, client.m_recvBufferOffset);
                            lock(client.m_recvLock){
                                client.m_recvQueue.Add(packet);
                            }
                            loop = true;
                        }
                    }
                }
                client.m_socket.BeginReceive(client.m_recvBuffer, client.m_recvBufferOffset, client.m_recvBufferLength, SocketFlags.None, new AsyncCallback(OnReciveAsync), client);
            } catch(Exception e){
                throw e;
            }
        }

        public void Send( TCPPacket p){
            lock (m_sendLock) {
                m_sendQueue.Add (p.Compile ());
            }
            if (m_state != TCPState.Sending)
                if (DoSend ())
                    m_state = TCPState.Sending;

        }

        private bool DoSend(){
            List<byte[]> data = null;
            lock (m_sendLock) {
                if(m_sendQueue.Capacity > 0){
                    data = m_sendQueue;
                    m_sendQueue = new List<byte[]> ();
                }
            }
            if (data == null)
                return false;

            int total_size = 0;
            foreach (byte[] d in data) {
                total_size += d.Length;
            }

            if (total_size == 0)
                return false;

            byte[] message = new byte[total_size];
            int pos = 0;
            foreach (byte[] d in data) {
                int len = d.Length;
                Buffer.BlockCopy(d, 0, message, pos, len);
                pos += len;
            }

            m_socket.BeginSend (message, 0, total_size, SocketFlags.None, new AsyncCallback (OnSended), this);
            return true;
        }

        private static void OnSended(IAsyncResult result){
            try{
                TCPClient client = (TCPClient)result.AsyncState;
                client.m_socket.EndSend(result);

                if(!client.DoSend()){
                    client.m_state = TCPState.Idle;
                }
            } catch(Exception e){
                throw e;
            }
        }

        public void ProcessQueue(){
            List<byte[]> data = null;
            lock (m_recvLock) {
                if( m_recvQueue.Count > 0){
                    data = m_recvQueue;
                    m_recvQueue = new List<byte[]>();
                }
            }

            if (data == null)
                return;

            foreach (byte[] packet_data in data) {
                PacketHandler handler = TCPPacketHandlers.GetHandler(packet_data[0]);
                PacketReader pr = new PacketReader(packet_data, handler.Length != 0);
                if(handler != null)
                    handler.OnReceive(pr);
            }
        }
    }
}

